const HexDataType = {
  name: 'Hex',
  stringToBytes(value) {
    if (!value) {
      return new Uint8Array([])
    }
    const chars = value.replaceAll(/\s*/g, '').toLowerCase().split('')
    const values = chars.map(c => {
      let value = 0
      const code = c.charCodeAt(0)
      if (code >= 48 && code <= 57) {
        value = code - 48
      } else if (code >= 97 && code <= 102) {
        value = 10 + code - 97
      }
      return value
    })
    const bytes = []
    for (let i = 0; i + 1 < values.length; i = i + 2) {
      bytes.push(values[i] * 16 + values[i + 1])
    }
    return new Uint8Array(bytes)
  },
  chars: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'],
  bytesToString(value) {
    const bytes = value ?? new Uint8Array([])
    const list = []
    for (let i = 0; i < bytes.byteLength; i++) {
      list.push(this.chars[((bytes[i] >> 4) & 0xf)] + this.chars[(bytes[i] & 0xf)])
    }
    return list.join('')
  }
}

const UTF8DataType = {
  name: 'UTF-8',
  encoder: new TextEncoder(),
  decoder: new TextDecoder('utf-8'),
  stringToBytes(value) {
    return this.encoder.encode(value)
  },
  bytesToString(value) {
    return this.decoder.decode(value)
  }
}

const ShortDataType = {
  name: 'Short',
  stringToBytes(value) {
    let shortValue = parseInt(value)
    if (Number.isNaN(shortValue)) {
      return new Uint8Array([0, 0])
    }
    shortValue = Math.min(32767, shortValue)
    shortValue = Math.max(-32768, shortValue)
    const buffer = new ArrayBuffer(2)
    new DataView(buffer).setInt16(0, shortValue, false)
    return new Uint8Array(buffer)
  },
  bytesToString(value) {
    const bytes = value ?? new Uint8Array([0, 0])
    return new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength).getInt16(0, false).toString()
  }
}

const IntDataType = {
  name: 'Int',
  stringToBytes(value) {
    let intValue = parseInt(value)
    if (Number.isNaN(intValue)) {
      return new Uint8Array([0, 0, 0, 0])
    }
    intValue = Math.min(2147483647, intValue)
    intValue = Math.max(-2147483648, intValue)
    const buffer = new ArrayBuffer(4)
    new DataView(buffer).setInt32(0, intValue, false)
    return new Uint8Array(buffer)
  },
  bytesToString(value) {
    const bytes = value ?? new Uint8Array([0, 0, 0, 0])
    return new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength).getInt32(0, false).toString()
  }
}

const LongDataType = {
  name: 'Long',
  stringToBytes(value) {
    let longValue = 0n
    try {
      longValue = BigInt(value)
    } catch {
      console.warn(value)
    }
    if (longValue > 9223372036854775807n) {
      longValue = 9223372036854775807n
    } else if (longValue < -9223372036854775808n) {
      longValue = -9223372036854775808n
    }
    const buffer = new ArrayBuffer(8)
    new DataView(buffer).setBigInt64(0, longValue, false)
    return new Uint8Array(buffer)
  },
  bytesToString(value) {
    const bytes = value ?? new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0])
    return new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength).getBigInt64(0, false).toString()
  }
}

const BooleanDataType = {
  name: 'Boolean',
  stringToBytes(value) {
    let booleanValue = false
    if (value) {
      booleanValue = 'true' === value.toString().toLowerCase()
    }
    return booleanValue ? new Uint8Array([-1]) : new Uint8Array([0])
  },
  bytesToString(value) {
    const bytes = value ?? new Uint8Array([-1])
    return (bytes.at(0) !== 0).toString()
  }
}

const FloatDataType = {
  name: 'Float',
  stringToBytes(value) {
    let floatValue = parseFloat(value)
    if (Number.isNaN(floatValue)) {
      return new Uint8Array([0, 0, 0, 0])
    }
    const buffer = new ArrayBuffer(4)
    new DataView(buffer).setFloat32(0, floatValue, false)
    return new Uint8Array(buffer)
  },
  bytesToString(value) {
    const bytes = value ?? new Uint8Array([0, 0, 0, 0])
    return new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength).getFloat32(0, false).toString()
  }
}

const DoubleDataType = {
  name: 'Double',
  stringToBytes(value) {
    let floatValue = parseFloat(value)
    if (Number.isNaN(floatValue)) {
      return new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0])
    }
    const buffer = new ArrayBuffer(8)
    new DataView(buffer).setFloat64(0, floatValue, false)
    return new Uint8Array(buffer)
  },
  bytesToString(value) {
    const bytes = value ?? new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0])
    return new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength).getFloat64(0, false).toString()
  }
}

const dataTypeMap = {
  [HexDataType.name]: HexDataType,
  [UTF8DataType.name]: UTF8DataType,
  [IntDataType.name]: IntDataType,
  [LongDataType.name]: LongDataType,
  [FloatDataType.name]: FloatDataType,
  [DoubleDataType.name]: DoubleDataType,
  [BooleanDataType.name]: BooleanDataType,
  [ShortDataType.name]: ShortDataType
}

function stringToBytes(dataType, value) {
  if (!dataType) {
    return HexDataType.stringToBytes(value)
  } else {
    return (dataTypeMap[dataType] ?? HexDataType).stringToBytes(value)
  }
}

function bytesToString(dataType, value) {
  if (!dataType) {
    return HexDataType.bytesToString(value)
  } else {
    return (dataTypeMap[dataType] ?? HexDataType).bytesToString(value)
  }
}

export default {
  dataTypes: [...Object.keys(dataTypeMap)],
  stringToBytes,
  bytesToString
}